﻿using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using NewRetail.Application.Components.Warehouse.Entity;
using NewRetail.Application.Core.Entity;
using System;
using System.Linq;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;

namespace NewRetail.Application.Core.SingleStore
{
    public interface IFilterSingleWarehouse<T>
    {
        IQueryable<T> FilterWarehouse(IQueryable<T> query, string warehouseId);
    }

    [ExposeServices(typeof(IFilterSingleWarehouse<Warehouse>))]
    public class WarehouseFilter : IFilterSingleWarehouse<Warehouse>, ITransientDependency
    {
        public IQueryable<Warehouse> FilterWarehouse(IQueryable<Warehouse> query, string warehouseId)
        {
            return query.Where(x => x.Id == warehouseId);
        }
    }

    [ExposeServices(typeof(IFilterSingleWarehouse<ChangeWarehouse>))]
    public class ChangeWarehouseFilter : IFilterSingleWarehouse<ChangeWarehouse>, ITransientDependency
    {
        public IQueryable<ChangeWarehouse> FilterWarehouse(IQueryable<ChangeWarehouse> query, string warehouseId)
        {
            return query.Where(x => x.inWarehouseID == warehouseId || x.outWarehouseID == warehouseId);
        }
    }

    public interface ISingleStoreService : IApplicationService
    {
        string CurrentStore { get; }

        bool TryGetCurrentStore(out string storeId);

        void SetCurrentStore(string storeId);

        IQueryable<T> ApplyToQueryableImpl<T>(IQueryable<T> query, string storeId) where T : IWarehouseEntity;

        IQueryable<T> ApplyToQueryable<T>(IQueryable<T> query);
    }

    public class SingleStoreService : MyApplicationService, ISingleStoreService
    {

        private readonly string Key = "user-current-store-id";
        private readonly IServiceProvider serviceProvider;
        private readonly IHttpContextAccessor httpContextAccessor;

        public SingleStoreService(IServiceProvider serviceProvider, IHttpContextAccessor httpContextAccessor)
        {
            this.serviceProvider = serviceProvider;
            this.httpContextAccessor = httpContextAccessor;
        }

        void CheckHttpContext()
        {
            this.httpContextAccessor.HttpContext.Should().NotBeNull(nameof(CheckHttpContext));
        }

        string GetCurrentStore()
        {
            this.CheckHttpContext();
            var res = this.httpContextAccessor.HttpContext.Items[this.Key]?.ToString();
            return res;
        }

        public string CurrentStore => this.GetCurrentStore();

        public bool TryGetCurrentStore(out string storeId)
        {
            storeId = this.CurrentStore;
            return !string.IsNullOrWhiteSpace(storeId);
        }

        public void SetCurrentStore(string storeId)
        {
            this.CheckHttpContext();

            if (new[] { "null", "undefined" }.Contains(storeId, StringComparer.OrdinalIgnoreCase))
            {
                storeId = string.Empty;
            }

            this.httpContextAccessor.HttpContext.Items[this.Key] = storeId;
        }

        public IQueryable<T> ApplyToQueryableImpl<T>(IQueryable<T> query, string storeId) where T : IWarehouseEntity
        {
            storeId.Should().NotBeNullOrEmpty();
            query = query.Where(x => x.warehouseID == storeId);
            return query;
        }

        public IQueryable<T> ApplyToQueryable<T>(IQueryable<T> query)
        {
            var storeId = this.GetCurrentStore();
            if (string.IsNullOrWhiteSpace(storeId))
            {
                return query;
            }

            var filters = this.serviceProvider.GetServices<IFilterSingleWarehouse<T>>();
            foreach (var f in filters)
            {
                query = f.FilterWarehouse(query, storeId);
            }

            //filter common warehouse fields
            var entityType = typeof(T);
            if (!entityType.IsAssignableTo<IWarehouseEntity>())
            {
                return query;
            }

            var implMethod = this.GetType().GetMethods().FirstOrDefault(x => x.Name == nameof(ApplyToQueryableImpl));
            implMethod.Should().NotBeNull();

            var res = implMethod.MakeGenericMethod(entityType).Invoke(
                obj: this,
                parameters: new object[] { query, storeId });

            if (res is IQueryable<T> filterdQuery)
            {
                return filterdQuery;
            }
            else
            {
                throw new BusinessException(message: nameof(ApplyToQueryable));
            }
        }
    }
}
